iT邦幫忙

2022 iThome 鐵人賽

DAY 9
0
Software Development

Kotlin on the way系列 第 9

Day 9 程式碼保衛戰 Defensive programming

  • 分享至 

  • xImage
  •  

It is my duty to protect my family
Mulan, 2020

台灣人透過文化累積,其實都有防禦性程式設計的概念----防禦駕駛,我們總是得預想每個用路人都會違規,以此保護自己

when ... else is for else

判斷條件 1,2,3 分別印出不同字串

when(number) {
    1 -> {"one"}
    2 -> {"two"}
    else -> {"three"}
}

我把範例的條件判斷最簡化了,應該很明顯,這種判斷遲早出錯的,根本就只判斷了 1,2 ,如標題所寫,when ... else 判斷是給給預期之外的情境的

stardand error

為什麼選擇標準函式庫的錯誤比較好呢?

難道是工程師懶惰打 code,台灣也要開始安靜辭職了嗎??不,人若不努力,跟鹹魚有什麼區別,但作為工程師,請聰明的工作,減少大家的工作量

回來主題,工程師們對於標準的錯誤有著相對一致的認知,也就是說和其他工程師合作時,丟出正確的標準錯誤可以讓協作者快速了解情況,而不是每次都得在專案全域搜尋客制的錯誤名稱

When to specific a custom error

當你實現的機制,已經不符合現有錯誤狀態時,請為此聲明客制錯誤,並對客制錯誤下註解,說明哪些情境會觸發,會有哪些影響,舉個例子吧

Kotlin Coroutine 裡面透過 CancellationException 來取消 coroutineScope

@SinceKotlin("1.4")
public actual typealias CancellationException = java.util.concurrent.CancellationException

@InlineOnly
@SinceKotlin("1.4")
public actual inline fun CancellationException(message: String?, cause: Throwable?): CancellationException {
    return CancellationException(message).also { it.initCause(cause) }
}
@InlineOnly
@SinceKotlin("1.4")
public actual inline fun CancellationException(cause: Throwable?): CancellationException {
    return CancellationException(cause?.toString()).also { it.initCause(cause) }
}

這是一個很好的設計,但這個客制錯誤不僅需要套件使用者了解這個錯誤,還需了解 套件的錯誤傳遞機制

Handle error is a task

檢查錯誤本身就是一件任務了,什麼意思呢?
如果一個函式裡面已經有 try/ catch/ finally,就不該再有其他任務執行

fun someTask(){
    try {
        
    } catch(e:Exception){
        Log.e(TAG, e)
        throw e
    }
}

而如果你的 catch 不對錯誤做處理,就應該把整個 try/ catch刪掉

但如果想用 try/ finally 來關閉資源呢?
可以用 kotlin.io 的 use 或 useline 方法

secret danger behind multiple platform

在 Kotlin 的設計中,我們需要和其他平台語言做互動,而這有可能產生出潛在的風險,比如 JAVA 吧,他廣為人知的就是 NullPointerException,儘管他有對應標示的 @NotNull 註釋,但如果外部函式庫沒有標示,Kotlin 編譯器依然會認定為 NotNull,儘管 null 在 Java 裡傳遞時沒事,但到了 Kotlin 的環境裡就處噴出 NullPointerException

另一個是 Kotlin/Js 的跨平台設置,個人的開發體驗來說還不如直接寫 Js
沒啦讚啦,但還是會有跨平台的小問題

比如 kotlin 可以定義 js 的函式


fun jsTypeOf(o: Any): String {
    return js("typeof o")
}
fun jsInfinite(): Int {
    return js("Number.POSITIVE_INFINITY")
}
fun jsIntOverFlow(): Int {
    return js("2147483647999")
}

儘管在讀的時候還行,但操作的時候就QQ


console.log(jsTypeOf(jsInfinite()))//number
console.log(jsInfinite())//Infinity
console.log(jsInfinite() - 100)//0
console.log(jsTypeOf(jsIntOverFlow()))//number
console.log(jsIntOverFlow())//2147483647999
console.log(jsIntOverFlow() - 100)//-101

真實的 js 結果會是

Number.Positive_Infinity - 100 // Infinity
2147483647999 - 100 //2147483647899

根本就 GG,所以寫跨平台操作時,要特別注意值的轉換

require, assert, check

斷言是另一個對錯誤情境的處理工具,和 try/ catch 不同的在於
斷言,是要檢查預期內的情境
錯誤,是要處理預期外的情境

那 require, assert, check 三者差在哪裡呢?
從 assert 開始了解

assert

主要用在測試的時候

@Test
fun `fib works correctly for the first 4 positions`() {
   assertEquals(1, fib(0))
   assertEquals(1, fib(1))
   assertEquals(2, fib(2))
   assertEquals(3, fib(3))
}

用於比較期望結果和實際結果是否一致

require

判斷參數 argument 的,在這邊多帶一個很重要的概念,物件是可以有狀態,他可以保有變數去記錄程式運行時的情境,而函示則無狀態,依照邏輯去完成某個任務

而這裡的 require 最常見的就是用於檢查,函式的傳入參數是否有效

fun isAgeGreaterThanZero(age:Int){
    require(age > 0){
        "Age shouldn't be a negetive number"
    }
}

那當我們輸入了-1時,就會出現

Exception in thread "main" java.lang.IllegalArgumentException: Age shouldn't be a negetive number
	at MainKt.isAgeGreaterThanZero(Main.kt:7)
	at MainKt.main(Main.kt:3)
	at MainKt.main(Main.kt)

check

而 check. 是被設計來檢查狀態的,一樣拿個例子

class UserConnectState(){
    var token:String? = null
        private set
    
    fun keepAlive(){
        checkNotNull(token){
            "Token is null, please login before connect to socket"
        }
        socket.startPingpong(token)
    }
}

English

Most Taiwanese have the concept of defensive programming ---- Defensive driving, we always predict every possibility that other driver will violate a traffic law

when ... else is for else

when 1,2,3 print different string

when(number) {
    1 -> {"one"}
    2 -> {"two"}
    else -> {"three"}
}

I simplified the check condition, it should be obvious, the check consition will cause issue soon or later, the else branch should throw unExcept error instaed

stardand error

Why choose standard error is better than custom one?

Is the engineer too lazy, start to quite-quit? Actually not, since all engineer has similar concept to standard error, by throw standard will be easier for other cooperator

When to specific a custom error

If you implement a condition, has error state not fit in any exists Error, please make a custom error, and add comment for it, specify what situation will trigger, what will happen next

In Kotlin Coroutine using CancellationException to cancel `coroutineScope ``

@SinceKotlin("1.4")
public actual typealias CancellationException = java.util.concurrent.CancellationException

@InlineOnly
@SinceKotlin("1.4")
public actual inline fun CancellationException(message: String?, cause: Throwable?): CancellationException {
    return CancellationException(message).also { it.initCause(cause) }
}
@InlineOnly
@SinceKotlin("1.4")
public actual inline fun CancellationException(cause: Throwable?): CancellationException {
    return CancellationException(cause?.toString()).also { it.initCause(cause) }
}

It is actually a great design, but it also need library user to know about the error and the error propagerta in the library

Handle error is a task

What does it mean that handling errors is a task?
If your function already have try/ catch/ finally, it should contain more logic

fun someTask(){
    try {
        
    } catch(e:Exception){
        Log.e(TAG, e)
        throw e
    }
}

And if you don't handle error in catch, consider delete the try/ catch block

What if you need try/ finally to close a resource?

using use or useline from kotlin.io

secret danger behind multiple platform

In the design of Kotlin, we need to interact with other platform language, and there is potential risk, take Java as example, it is famous about NullPointerException, although java has @NotNull, but if the author doesn't mark it, the compiler will assume it is NotNull, which will cause error in kotlin part

The other one is Kotlin/ js, personally I recommend to write js directory

There are some issue between those

we can define js function in kotlin


fun jsTypeOf(o: Any): String {
    return js("typeof o")
}
fun jsInfinite(): Int {
    return js("Number.POSITIVE_INFINITY")
}
fun jsIntOverFlow(): Int {
    return js("2147483647999")
}

Although reading seems normal, but if we try to modify it...


console.log(jsTypeOf(jsInfinite()))//number
console.log(jsInfinite())//Infinity
console.log(jsInfinite() - 100)//0
console.log(jsTypeOf(jsIntOverFlow()))//number
console.log(jsIntOverFlow())//2147483647999
console.log(jsIntOverFlow() - 100)//-101

the result of javascript is

Number.Positive_Infinity - 100 // Infinity
2147483647999 - 100 //2147483647899

It is not safe to trust value from other platform without check

require, assert, check

Assertion is the other tool for exception handling, the difference between try/ catch
assertion, check for expected situation
Error, handle situation not expected

Then what is the difference between require, assert, check?

assert

Mainly use for test

@Test
fun `fib works correctly for the first 4 positions`() {
   assertEquals(1, fib(0))
   assertEquals(1, fib(1))
   assertEquals(2, fib(2))
   assertEquals(3, fib(3))
}

to check is the expected and actual value is same

require

check the argument, one idea to bring up, object could be stateful, it can keep the state at run time, and function does not

The required here is to check the input argument is valid or not

fun isAgeGreaterThanZero(age:Int){
    require(age > 0){
        "Age shouldn't be a negative number"
    }
}

If we pass in -1

Exception in thread "main" java.lang.IllegalArgumentException: Age shouldn't be a negative number
	at MainKt.isAgeGreaterThanZero(Main.kt:7)
	at MainKt.main(Main.kt:3)
	at MainKt.main(Main.kt)

check

And check is build for check state

class UserConnectState(){
    var token:String? = null
        private set
    
    fun keepAlive(){
        checkNotNull(token){
            "Token is null, please login before connect to socket"
        }
        socket.startPingpong(token)
    }
}

上一篇
Day 8 Mutability 是把雙面刃 Mutability is double edged sword
下一篇
Day 10 想回到那天和那場電影 (feat. Git) Wish to back to the night with movie (feat. Git)
系列文
Kotlin on the way31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言